#include <ps2.h>

#define PS2_KC_BKSP    0x08
#define PS2_KC_UP      0x81
#define PS2_KC_DOWN    0x82
#define PS2_KC_LEFT    0x83
#define PS2_KC_RIGHT   0x84
#define PS2_KC_PGDN    0x85
#define PS2_KC_PGUP    0x86
#define PS2_KC_END     0x87
#define PS2_KC_HOME    0x88
#define PS2_KC_INS     0x89
#define PS2_KC_DEL     0x8A
#define PS2_KC_ESC     0x8B
#define PS2_KC_CLON    0x8C // caps_lock on
#define PS2_KC_CLOFF   0x8D // caps_lock off

/*
 * Pin 4 is the ps2 data pin, pin 3 is the clock pin
 */

PS2 kbd(3, 4);
bool ps2Keyboard_release = false;
bool ps2Keyboard_extend = false;
bool cmd_ack_byte_ok = false;
bool ps2Keyboard_shift = false;
bool ps2Keyboard_alt = false;
bool ps2Keyboard_caps_lock = false;
bool ps2Keyboard_ctrl = false;

byte ps2Keyboard_CharBuffer = 0;

byte code;

// val : bit_2=caps_lock, bit_1=num_lock, bit_0=scroll_lock
void kbd_set_lights(byte val) {
  kbd.write(0xED);   
  kbd.read(); // pull out the ack byte
  kbd.write(val);     // now send the data
}

byte lookupCode() {
  byte result = ps2Keyboard_CharBuffer; 
  
  // Use a switch for the code to character conversion.
  // This is fast and actually only uses 4 bytes per simple line
  switch (result) {
  case 0x1C: result = 'a'; break;
  case 0x32: result = 'b'; break;
  case 0x21: result = 'c'; break;
  case 0x23: result = 'd'; break;
  case 0x24: result = 'e'; break;
  case 0x2B: result = 'f'; break;
  case 0x34: result = 'g'; break;
  case 0x33: result = 'h'; break;
  case 0x43: result = 'i'; break;
  case 0x3B: result = 'j'; break;
  case 0x42: result = 'k'; break;
  case 0x4B: result = 'l'; break;
  case 0x3A: result = 'm'; break;
  case 0x31: result = 'n'; break;
  case 0x44: result = 'o'; break;
  case 0x4D: result = 'p'; break;
  case 0x15: result = 'q'; break;
  case 0x2D: result = 'r'; break;
  case 0x1B: result = 's'; break;
  case 0x2C: result = 't'; break;
  case 0x3C: result = 'u'; break;
  case 0x2A: result = 'v'; break;
  case 0x1D: result = 'w'; break;
  case 0x22: result = 'x'; break;
  case 0x35: result = 'y'; break;
  case 0x1A: result = 'z'; break;

    // note that caps lock only used on a-z
  case 0x41: result = ps2Keyboard_shift? '<' : ','; break;
  case 0x49: result = ps2Keyboard_shift? '>' : '.'; break;
  case 0x4A: result = ps2Keyboard_shift? '?' : '/'; break;
  case 0x54: result = ps2Keyboard_shift? '{' : '['; break;
  case 0x5B: result = ps2Keyboard_shift? '}' : ']'; break;
  case 0x4E: result = ps2Keyboard_shift? '_' : '-'; break;
  case 0x55: result = ps2Keyboard_shift? '+' : '='; break;
  case 0x29: result = ' '; break;

  case 0x45: result = ps2Keyboard_shift? ')' : '0'; break;
  case 0x16: result = ps2Keyboard_shift? '!' : '1'; break;
  case 0x1E: result = ps2Keyboard_shift? '@' : '2'; break;
  case 0x26: result = ps2Keyboard_shift? '' : '3'; break;
  case 0x25: result = ps2Keyboard_shift? '$' : '4'; break;
  case 0x2E: result = ps2Keyboard_shift? '%' : '5'; break;
  case 0x36: result = ps2Keyboard_shift? '^' : '6'; break;
  case 0x3D: result = ps2Keyboard_shift? '&' : '7'; break;
  case 0x3E: result = ps2Keyboard_shift? '*' : '8'; break;
  case 0x46: result = ps2Keyboard_shift? '(' : '9'; break;

  case 0x0D: result = '\t'; break;
  case 0x5A: result = '\n'; break;
  case 0x66: result = PS2_KC_BKSP;  break;
  case 0x69: result = ps2Keyboard_extend? PS2_KC_END   : '1'; break;
  case 0x6B: result = ps2Keyboard_extend? PS2_KC_LEFT  : '4'; break;
  case 0x6C: result = ps2Keyboard_extend? PS2_KC_HOME  : '7'; break;
  case 0x70: result = ps2Keyboard_extend? PS2_KC_INS   : '0'; break;
  case 0x71: result = ps2Keyboard_extend? PS2_KC_DEL   : '.'; break;
  case 0x72: result = ps2Keyboard_extend? PS2_KC_DOWN  : '2'; break;
  case 0x73: result = '5'; break;
  case 0x74: result = ps2Keyboard_extend? PS2_KC_RIGHT : '6'; break;
  case 0x75: result = ps2Keyboard_extend? PS2_KC_UP    : '8'; break;
  case 0x76: result = PS2_KC_ESC; break;
  case 0x79: result = '+'; break;
  case 0x7A: result = ps2Keyboard_extend? PS2_KC_PGDN  : '3'; break;
  case 0x7B: result = '-'; break;
  case 0x7C: result = '*'; break;
  case 0x7D: result = ps2Keyboard_extend? PS2_KC_PGUP  : '9'; break;

    // Reset the shift counter for unexpected values, to get back into sync
    // This allows for hot plugging a keyboard in and out
  default:  delay(500); // but wait a bit in case part way through a shift
            ps2Keyboard_shift      = false;
            ps2Keyboard_ctrl       = false;
            ps2Keyboard_alt        = false;
            ps2Keyboard_extend     = false;
            ps2Keyboard_release    = false;
            ps2Keyboard_caps_lock  = false;
  } // end switch(result)

  // shift a-z chars here (less code than in the switch statement)
  if (((result>='a') && (result<='z')) &&
      ((ps2Keyboard_shift && !ps2Keyboard_caps_lock) ||
       (!ps2Keyboard_shift && ps2Keyboard_caps_lock))) {
    result = result + ('A'-'a');
  }

  // done with the character
  ps2Keyboard_CharBuffer = 0;

  return(result);
}

void kbd_init()
{
  char ack;

  kbd.write(0xff);  // send reset code
  ack = kbd.read();  // byte, kbd does self test
  ack = kbd.read();  // another ack when self test is done
}

void setup()
{
  Serial.begin(19200); //19200 bps used by iphone connector
  kbd_init();
}

/*
 * get a keycode from the kbd and report it back to the
 * host via the serial line.
 */
void loop()
{
  
    /* read a keycode */
    code = kbd.read();
    
    if( !code ) return; // ignore null bytes
    
    /* convert from scan code */
    switch (code) {
    case 0xF0: { // key release char
      ps2Keyboard_release = true;
      ps2Keyboard_extend = false;
      break;
    }
    case 0xFA: { // command acknowlegde byte
      cmd_ack_byte_ok = true;
      break;
    }
    case 0xE0: { // extended char set
      ps2Keyboard_extend = true;
      break;
    }
    case 0x12:   // left shift
    case 0x59: { // right shift
      ps2Keyboard_shift = ps2Keyboard_release? false : true;
      ps2Keyboard_release = false;
      break;
    }
    case 0x11: { // alt key (right alt is extended 0x11)
      ps2Keyboard_alt = ps2Keyboard_release? false : true;
      ps2Keyboard_release = false;
      break;
    }
    case 0x14: { // ctrl key (right ctrl is extended 0x14)
      ps2Keyboard_ctrl = ps2Keyboard_release? false : true;
      ps2Keyboard_release = false;
      break;
    }
    case 0x58: { // caps lock key
      if (!ps2Keyboard_release) {
	ps2Keyboard_caps_lock = ps2Keyboard_caps_lock? false : true;
	// allow caps lock code through to enable light on and off
        if (ps2Keyboard_caps_lock) kbd_set_lights(4);
        else                       kbd_set_lights(0);
      }
      else {
	ps2Keyboard_release = false;
      }
      break;
    }
    default: { // not a control key
      if (ps2Keyboard_release) { // ignore releases
        ps2Keyboard_release = false;
      }
      else { // real keys get sent out
        ps2Keyboard_CharBuffer = code;
        Serial.println(lookupCode(), BYTE);
      }
    }
  }
}
